package dabs.engine.plant;

import java.io.Serializable;
import java.util.ArrayList;

public class HydraulicState implements Serializable {
	private static final long serialVersionUID = -7872945509964016537L;
	
	private int waterLevel;
	private int steamLevel;
	
	public int getWaterLevel() { return waterLevel; }
	public int getSteamLevel() { return steamLevel; }
	
	//Takes the input and adds it to the relevant level
	public void receive(int quantity, boolean isWater) {
		if (isWater) waterLevel += quantity;
		else steamLevel += quantity;
	}
	
	//Only called by Reactor and Condenser, does the actual conversion within the component and then calls
	//sentToNext to output contents
	public void convert(int conversionRate, boolean steamToWater) {

		if (steamToWater) {//If converting steam to water
			conversionRate = getLevelChange(conversionRate, steamLevel);
			waterLevel += conversionRate;
			steamLevel -= conversionRate; }
		else {//If converting water to steam
			conversionRate = getLevelChange(conversionRate, waterLevel);
			steamLevel += conversionRate;
			waterLevel -= conversionRate; }
	}
	
	//Sends either steam or water depending on steamToWater to it's components
	public void sendToNext(int levelChange, boolean steamToWater, ArrayList<ComponentPart> outputs, int selfPressure) {
		outputs = getActiveOutputs(outputs);
		int count = outputs.size();
		
		if (count == 0) return;
		
		//Calculates the average pressure of the activeOutputs
		int outputsPressure = 0;
		for (ComponentPart output : outputs) {
			outputsPressure += output.getPressure();
		}
		outputsPressure = outputsPressure / count;
		//Calculate the new levelChange based on pressure
		if (outputsPressure == 0) levelChange *= 2;
		else if ((selfPressure / outputsPressure) < 0.5) levelChange *= 0.5;
		else if ((selfPressure / outputsPressure) > 2) levelChange *= 2;
		else levelChange = (selfPressure / outputsPressure) * levelChange;
		
		//Number of active outputs will only ever be 0, 1 or 2 in this model
		if (count > 0) { //If there are active outputs
			if (steamToWater) {//Reduce the water level
				levelChange = getLevelChange(levelChange, waterLevel);
				waterLevel -= levelChange; }
			else {//Or the steam level according to steamToWater
				levelChange = getLevelChange(levelChange, steamLevel);
				steamLevel -= levelChange; }
			
			//If only one active output, send all of levelChange to that ComponentPart
			if (count == 1) outputs.get(0).getNewHydroState().receive(levelChange, steamToWater);
			//If there are two active outputs, the levelChange is dependent on the pressure difference between the two
			if (count == 2) {
				//This ensures that the number of units passed on is always equal to the original value
				//of levelChange, guaranteeing no units to be randomly lost or created due to rounding
				int amount;
				if (outputs.get(0).getPressure() + outputs.get(1).getPressure() == 0) amount = (int) (levelChange * 0.5);
				else amount = (int) (outputs.get(1).getPressure() * (levelChange / (outputs.get(0).getPressure() + outputs.get(1).getPressure())));
				outputs.get(0).getNewHydroState().receive(amount, steamToWater);
				levelChange -= amount;
				outputs.get(1).getNewHydroState().receive(levelChange, steamToWater); }}
	}
	
	//Ensures that each ComponentPart cannot output more units than it currently contains
	public int getLevelChange(int levelChange, int unit) {
		if (unit < levelChange) return unit;
		else return levelChange;
	}
	public ArrayList<ComponentPart> getActiveOutputs(ArrayList<ComponentPart> outputs) {
		ArrayList<ComponentPart> activeOutputs = new ArrayList<ComponentPart>();
		for (ComponentPart part : outputs) { //Cycle through each ComponentPart in outputs
			//Pumps and Valves always get passed contents
			if (part instanceof Pump || part instanceof Valve) activeOutputs.add(part);
			else if (!part.isDead()) activeOutputs.add(part); //Any other component will only be passed if alive
		}
		return activeOutputs; //Return the generated list
	}
	
	public String toString() {
		return String.format("water: %d, steam: %d", waterLevel, steamLevel); 
	}
}
